#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<stdio.h>
#include<netdb.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<dirent.h>
#include<pthread.h>
#include <limits.h>
#include <sys/time.h>
#include <signal.h>
#include "peer_thread.h"
#include "tracker_thread.h"
#include "peer.h"
#include "tad.h"
#include "md5.h"


  /****
    Infos lues depuis le fichier de config
  ****/
  char tracker_ip[] = "127.0.0.1";
  int tracker_port = 65000;
  int listening_port;
  int update_delay = 60;
  /****
    Fin fichier config
  ****/
/****************/



/* VAR GLOBALES */
struct peer *hashmap_peers = NULL;
struct file *hashmap_lfiles = NULL;
struct file *hashmap_sfiles = NULL;
pthread_t peers[NUM_MAX_PEERS];
pthread_t tracker;
int tracker_socket;
struct itimerval timer;
struct sigaction sig;

extern void update_tracker(void);
extern void update_peers(void);
extern pthread_mutex_t tracker_mutex;

void list_peers(void) {
  struct peer *p = hashmap_peers;
  printf("**** PEERS:\n");
  while(p)
    printf("\t%s thread num %i\n", p->key, p->thread_id);
  printf("***********\n");
}

void updates(int signum) {
  printf("Sending updates...\n");
  update_tracker();
//  update_peers();
}

void update_tracker(void) {
  // update seed [$Key1 $Key2 $Key3 …] leech [$Key10 $Key11 $Key12 …]
  pthread_mutex_lock (&tracker_mutex);
  char cmd[BUF_SIZE];
  sprintf(cmd, "update seed [ ");
  write(tracker_socket, cmd, strlen(cmd));
    
  struct file *files = hashmap_sfiles;
  while(files!=NULL) {
    write(tracker_socket, files->name, strlen(files->name));
    write(tracker_socket, " ", 1);
    files = files->next;
  }
  
  write(tracker_socket, "] leech [ ", 10);
  
  files = hashmap_lfiles;
  while(files!=NULL) {
    write(tracker_socket, files->name, strlen(files->name));
    write(tracker_socket, " ", 1);
    files = files->next;
  }
  
  write(tracker_socket, "] ", 2);
}


void connect_and_ask(char *ip, char *port, char *fkey) {
  //printf("connect_and_ask %s:%s (%s)", ip, port, fkey);
  char pkey[IPPORT_LENGTH];
  sprintf(pkey, "%d", listening_port);
  if(!strcmp(port, pkey)) return; // TODO A corriger (virer le demandeur du résultat dans getfile tracker)
  
  sprintf(pkey, "%s:%s", ip, port);
  struct peer *p = find_peer(pkey);
  
  struct file *f = find_file(fkey, hashmap_sfiles);
  if(!f) f = find_file(fkey, hashmap_lfiles);
  
  
  if(!f) {
    f = calloc(sizeof(*f), 1);
    strcpy(f->key, fkey);
    
  }
  else
    printf("%s file already exists\n", fkey);

  
  // TODO normalement faut aussi remplir f->peers mais faut faire une copie de p pour pas écraser le next
  
  if(!p) {
    p = calloc(sizeof(*p), 1);
    strcpy(p->key, pkey);
    strcpy(p->ip, ip);
    strcpy(p->port, port);
    CHKZ(pthread_create(&tracker, NULL, peer_thread, p))
    add_peer(p);
  }
  
  // Insertion en tête du fichier à leecher
  struct file_list *fl = malloc(sizeof(*fl));
  strcpy(fl->key, fkey);
  fl->next =  p->leeched_files;
  p->leeched_files = fl;
  
}


void announce(void) {
  //announce listen $Port seed [$Filename1 $Length1 $PieceSize1 $Key1 $Filename2 $Length2 $PieceSize2 $Key2 …] leech [$Key3 $Key4 …]
  pthread_mutex_lock (&tracker_mutex);
  
  char cmd[BUF_SIZE];
  sprintf(cmd, "announce listen %d seed [ ", listening_port);
  write(tracker_socket, cmd, strlen(cmd));
  
  struct file *files = hashmap_sfiles;
  while(files!=NULL) {
    sprintf(cmd, "%s %ld %d %s ", files->name, files->file_size, files->piece_size, files->key);
    write(tracker_socket, cmd, strlen(cmd));
    files = files->next;
  }
  sprintf(cmd, "] leech [ ");
  write(tracker_socket, cmd, strlen(cmd));
  files = hashmap_lfiles;
  while(files!=NULL) {
    sprintf(cmd, "%s %ld %d %s ", files->name, files->file_size, files->piece_size, files->key);
    write(tracker_socket, cmd, strlen(cmd));
    files = files->next;
  }
  sprintf(cmd, "] ");
  write(tracker_socket, cmd, strlen(cmd));

  pthread_mutex_unlock (&tracker_mutex);
}

int gargc;
char **gargv;
int main(int argc, char **argv) {
   listening_port = argc>1? atoi(argv[1]) : 65001;
   gargc = argc; gargv = argv;
  
  int clilen = sizeof(struct sockaddr_in);
  int peer, s, i;
  int next_index_peer = 0;   /* Next index available in the peers table */

  struct sockaddr_in that;
	that.sin_family = AF_INET;
	that.sin_port = htons(listening_port);
	that.sin_addr.s_addr = INADDR_ANY;
  CHKN((s = socket(AF_INET, SOCK_STREAM, 0)));
  CHKN(bind(s, (struct sockaddr *)&that, sizeof(struct sockaddr_in)))
  CHKN(listen(s, NUM_MAX_PEERS))
  
  /******** REMPLIR LES STRUCTURES DE DONNEES *****/
    DIR* rep = NULL;
    struct dirent* fichierLu = NULL; /* Déclaration d'un pointeur vers la structure dirent. */
    rep = opendir(SHARING_PATH);
    if (rep == NULL) { perror("Unable to open shared folder"); exit(-1); }
    char key[HASH_LENGTH+1];
    
    while((fichierLu = readdir(rep))) {
      if(fichierLu->d_type == DT_REG) {
        md5_hash_from_string(fichierLu->d_name, key);

        struct file *f = malloc(sizeof(*f));
        strcpy(f->key, key);
        strcpy(f->name, fichierLu->d_name);
        char cmd[BUF_SIZE];
        strcpy(cmd, SHARING_PATH);
        strcat(cmd, "/");
        strcat(cmd, fichierLu->d_name);
        long file_size=0; fsize(cmd, &file_size);
        f->file_size = file_size;
        f->piece_size = PIECE_SIZE;
        for(i=0; i<f->file_size/f->piece_size; i++)
          f->buffermap[i] = CHAR_MAX;
        f->peers = NULL;
        hashmap_lfiles = add_file(f, hashmap_lfiles);
        printf("%s %ld Bytes (%ld pieces) (%s) added to shared files list\n", cmd, f->file_size, (f->file_size/f->piece_size)? f->file_size/f->piece_size:1, f->key);
      }
    }
    printf("scan complete\n");
    if (closedir(rep) == -1)
        exit(-1);
        
  //REMPLIR()
  /******** FIN REMPLIR *****/
  
  /************ Connexion au tracker **************/
  int *null = NULL;
  pthread_mutex_init (&tracker_mutex, NULL);
  pthread_mutex_lock (&tracker_mutex); // Ne pas autoriser d'écriture tant que thread non initialisé
  CHKZ(pthread_create(&tracker, NULL, tracker_thread, null))
  /***************** Fin tracker ******************/
  
  /************ Timers de mise à jour ************/
	getitimer(ITIMER_REAL, &timer);
  timer.it_interval.tv_sec = update_delay;
  timer.it_interval.tv_usec = 0;
  timer.it_value.tv_sec = update_delay;
  timer.it_value.tv_usec = 0;
  
  setitimer(ITIMER_REAL, &timer, NULL);
   
  sigemptyset (&sig.sa_mask);
	sig.sa_handler = updates;
  sig.sa_flags = 0;
  sigaction(SIGALRM, &sig, NULL);
  /***************** Fin timers ******************/
  
  
  for(;;) {
    if(next_index_peer==NUM_MAX_PEERS) {
      printf("Server full\n");
      break;
    }
    else {
      peer = accept(s, (struct sockaddr *)&that, (socklen_t *)&clilen);
      if(peer>0) {
        printf("New connection accepted\n");
   
        struct peer *p = malloc(sizeof(*p));
        strcpy(p->key, "86.86.86.86:1234");
        strcpy(p->ip, "86.86.86.86");
        strcpy(p->port, "1234");
        p->thread_id = next_index_peer++;
        p->fd = peer;
        add_peer(p);

        CHKZ(pthread_create(&peers[p->thread_id], NULL, peer_thread, (void *)p))
      }
    }
  }

  
  void *v = NULL;
  for(i=0; i<next_index_peer; i++) 
    pthread_join(peers[i], &v);

  return EXIT_SUCCESS;
}
